#include "wx/joyedit.h"

// FIXME: suppport analog/digital flag on per-axis basis

IMPLEMENT_DYNAMIC_CLASS(wxJoyKeyTextCtrl, wxKeyTextCtrl)

BEGIN_EVENT_TABLE(wxJoyKeyTextCtrl, wxKeyTextCtrl)
    EVT_SDLJOY(wxJoyKeyTextCtrl::OnJoy)
END_EVENT_TABLE()

int wxJoyKeyTextCtrl::DigitalButton(wxSDLJoyEvent &event)
{
    int sdlval = event.GetControlValue();
    int sdltype = event.GetControlType();

    switch(sdltype) {
    case WXSDLJOY_AXIS:
	// for val = 0 return arbitrary direction; val means "off"
	return sdlval > 0 ? WXJB_AXIS_PLUS : WXJB_AXIS_MINUS;
    case WXSDLJOY_HAT:
	/* URDL = 1248 */
	switch(sdlval) {
	case 1:
	    return WXJB_HAT_N;
	case 2:
	    return WXJB_HAT_E;
	case 3:
	    return WXJB_HAT_NE;
	case 4:
	    return WXJB_HAT_S;
	case 6:
	    return WXJB_HAT_SE;
	case 8:
	    return WXJB_HAT_W;
	case 9:
	    return WXJB_HAT_NW;
	case 12:
	    return WXJB_HAT_SW;
	default:
	    return WXJB_HAT_N; // arbitrary direction; val = 0 means "off"
	}
    case WXSDLJOY_BUTTON:
	return WXJB_BUTTON;
    default:
	// unknown ctrl type
	return -1;
    }
}

void wxJoyKeyTextCtrl::OnJoy(wxSDLJoyEvent &event)
{
    short val = event.GetControlValue();
    int mod = DigitalButton(event);
    int key = event.GetControlIndex(), joy = event.GetJoy() + 1;

    if(!val || mod < 0)
	return;
    wxString nv = ToString(mod, key, joy);
    if(nv.empty())
	return;
    if(multikey) {
	wxString ov = GetValue();
	if(!ov.empty())
	    nv = ov + multikey + nv;
    }
    SetValue(nv);
    if(keyenter)
	Navigate();
}

wxString wxJoyKeyTextCtrl::ToString(int mod, int key, int joy)
{
    if(!joy)
	return wxKeyTextCtrl::ToString(mod, key);
    wxString s;
    // Note: wx translates unconditionally (2.8.12, 2.9.1)!
    // So any strings added below must also be translated unconditionally
    s.Printf(_("Joy%d-"), joy);
    wxString mk;
    switch(mod) {
    case WXJB_AXIS_PLUS:
	mk.Printf(_("Axis%d+"), key);
	break;
    case WXJB_AXIS_MINUS:
	mk.Printf(_("Axis%d-"), key);
	break;
    case WXJB_BUTTON:
	mk.Printf(_("Button%d"), key);
	break;
    case WXJB_HAT_N:
	mk.Printf(_("Hat%dN"), key);
	break;
    case WXJB_HAT_S:
	mk.Printf(_("Hat%dS"), key);
	break;
    case WXJB_HAT_W:
	mk.Printf(_("Hat%dW"), key);
	break;
    case WXJB_HAT_E:
	mk.Printf(_("Hat%dE"), key);
	break;
    case WXJB_HAT_NW:
	mk.Printf(_("Hat%dNW"), key);
	break;
    case WXJB_HAT_NE:
	mk.Printf(_("Hat%dNE"), key);
	break;
    case WXJB_HAT_SW:
	mk.Printf(_("Hat%dSW"), key);
	break;
    case WXJB_HAT_SE:
	mk.Printf(_("Hat%dSE"), key);
	break;
    }
    s += mk;
    return s;
}

wxString wxJoyKeyTextCtrl::ToString(wxJoyKeyBinding_v keys, wxChar sep)
{
    wxString ret;
    for(int i = 0; i < keys.size(); i++) {
	if(i > 0)
	    ret += sep;
	wxString key = ToString(keys[i].mod, keys[i].key, keys[i].joy);
	if(key.empty())
	    return wxEmptyString;
	ret += key;
    }
    return ret;
}

#include <wx/regex.h>

// only parse regex once
// Note: wx translates unconditionally (2.8.12, 2.9.1)!
// So any strings added below must also be translated unconditionally
// \1 is joy #
static wxRegEx joyre(_("^Joy([0-9]+)[-+]"), wxRE_EXTENDED|wxRE_ICASE);
// \1 is axis# and \2 is + or -
static wxRegEx axre(_("Axis([0-9]+)([+-])"), wxRE_EXTENDED|wxRE_ICASE);
// \1 is button#
static wxRegEx butre(_("Button([0-9]+)"), wxRE_EXTENDED|wxRE_ICASE);
// \1 is hat#, \3 is N, \4 is S, \5 is E, \6 is W, \7 is NE, \8 is SE,
// \9 is SW, \10 is NW
static wxRegEx hatre(_("Hat([0-9]+)((N|North|U|Up)|(S|South|D|Down)|"
		       "(E|East|R|Right)|(W|West|L|Left)|"
		       "(NE|NorthEast|UR|UpRight)|(SE|SouthEast|DR|DownRight)|"
		       "(SW|SouthWest|DL|DownLeft)|(NW|NorthWest|UL|UpLeft))"),
		     wxRE_EXTENDED|wxRE_ICASE);
// use of static wxRegeEx is not thread-safe
static wxCriticalSection recs;

// wx provides no atoi for wxChar
// this is not a universal function; assumes valid number
static int simple_atoi(const wxChar *s, int len)
{
    int ret = 0;
    for(int i = 0; i < len; i++)
	ret = ret * 10 + s[i] - wxT('0');
    return ret;
}

static bool ParseJoy(const wxChar *s, int len, int &mod, int &key, int &joy)
{
    mod = key = joy = 0;
    if(!len)
	return false;

    wxCriticalSectionLocker lk(recs);
    size_t b, l;
    if(!joyre.Matches(s) || !joyre.GetMatch(&b, &l) || b)
	return false;
    const wxChar *p = s + l;
    int alen = len - l;
    joyre.GetMatch(&b, &l, 1);
    joy = simple_atoi(s + b, l);
#define is_ctrl(re) re.Matches(p) && re.GetMatch(&b, &l) && l == alen && !b
    if(is_ctrl(axre)) {
	axre.GetMatch(&b, &l, 1);
	key = simple_atoi(p + b, l);
	axre.GetMatch(&b, &l, 2);
	mod = p[b] == wxT('+') ? WXJB_AXIS_PLUS : WXJB_AXIS_MINUS;
    } else if(is_ctrl(butre)) {
	butre.GetMatch(&b, &l, 1);
	key = simple_atoi(p + b, l);
	mod = WXJB_BUTTON;
    } else if(is_ctrl(hatre)) {
	hatre.GetMatch(&b, &l, 1);
	key = simple_atoi(p + b, l);
#define check_dir(n, d) else if(hatre.GetMatch(&b, &l, n) && l > 0) mod = WXJB_HAT_##d
	if(0);
	check_dir(3, N);
	check_dir(4, S);
	check_dir(5, E);
	check_dir(6, W);
	check_dir(7, NE);
	check_dir(8, SE);
	check_dir(9, SW);
	check_dir(10, NW);
    } else {
	joy = 0;
	return false;
    }
    return true;
}

bool wxJoyKeyTextCtrl::ParseString(const wxChar *s, int len, int &mod, int &key, int &joy)
{
    if(ParseJoy(s, len, mod, key, joy))
	return true;
    return wxKeyTextCtrl::ParseString(s, len, mod, key);
}

bool wxJoyKeyTextCtrl::FromString(const wxString &s, int &mod, int &key, int &joy)
{
    return ParseString(s.c_str(), s.size(), mod, key, joy);
}

wxJoyKeyBinding_v wxJoyKeyTextCtrl::FromString(const wxString &s, wxChar sep)
{
    wxJoyKeyBinding_v ret, empty;
    int mod, key, joy;
    int len = s.size();
    if(!len)
	return empty;

    for(int lastkey = len - 1; (lastkey = s.rfind(sep, lastkey)) != wxString::npos; lastkey--) {
	if(lastkey == len - 1) {
	    // sep as accel
	    if(!lastkey)
		break;
	    if(s[lastkey - 1] == wxT('-') || s[lastkey - 1] == wxT('+') ||
	       s[lastkey - 1] == sep)
		continue;
	}
	if(!ParseString(s.c_str() + lastkey + 1, len - lastkey - 1, mod, key, joy))
	    return empty;
	wxJoyKeyBinding jb = { key, mod, joy };
	ret.insert(ret.begin(), jb);
	len = lastkey;
    }
    if(!ParseString(s.c_str(), len, mod, key, joy))
	return empty;
    wxJoyKeyBinding jb = { key, mod, joy };
    ret.insert(ret.begin(), jb);
    return ret;
}

IMPLEMENT_CLASS(wxJoyKeyValidator, wxValidator)

bool wxJoyKeyValidator::TransferToWindow()
{
    if(!val)
	return false;
    wxJoyKeyTextCtrl *jk = wxDynamicCast(GetWindow(), wxJoyKeyTextCtrl);
    if(!jk)
	return false;
    jk->SetValue(wxJoyKeyTextCtrl::ToString(*val));
    return true;
}

bool wxJoyKeyValidator::TransferFromWindow()
{
    if(!val)
	return false;
    wxJoyKeyTextCtrl *jk = wxDynamicCast(GetWindow(), wxJoyKeyTextCtrl);
    if(!jk)
	return false;
    *val = wxJoyKeyTextCtrl::FromString(jk->GetValue());
    return true;
}
